/*
 * MIT License
 *
 * Copyright (c) 2019 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.commons.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.file.FileAlreadyExistsException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

import org.jetbrains.annotations.NotNull;

import cn.seaboot.commons.core.Asserts;

/**
 * 功能与IOUtils类似，主要针对文件相关的，做了功能上的扩展，
 * <p>
 * 使用结束，IO不关闭，需要自己处理
 * 已经处理文件是否存在的判断
 *
 * @author Mr.css 2018-07-13 11:32:57
 */
public class FileUtils {
    private FileUtils() {
    }

    /**
     * Returns the path to the user's home directory.
     *
     * @return the path to the user's home directory.
     */
    public static String getUserDirectoryPath() {
        return System.getProperty("user.home");
    }

    /**
     * Returns a {@link File} representing the user's home directory.
     *
     * @return the user's home directory.
     */
    public static File getUserDirectory() {
        return new File(getUserDirectoryPath());
    }

    /**
     * 递归遍历所有文件
     *
     * @param root 文件夹
     */
    public static void eachFile(File root, Consumer<File> compiler) {
        File[] files = root.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    compiler.accept(file);
                } else if (file.isDirectory()) {
                    eachFile(file, compiler);
                }
            }
        }
    }

    /**
     * 递归遍历所有文件夹
     *
     * @param root 文件夹
     */
    public static void eachDirectory(File root, Consumer<File> compiler) {
        File[] files = root.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    compiler.accept(file);
                    eachDirectory(file, compiler);
                }
            }
        }
    }

    /**
     * 非递归算法，遍历文件夹下的所有文件
     *
     * @param root 文件夹
     * @param fun  映射函数
     * @return 将文件对象映射成另一个对象
     */
    public static <R> List<R> map(File root, Function<File, R> fun) {
        List<R> result = new ArrayList<>();
        eachFile(root, file -> result.add(fun.apply(file)));
        return result;
    }

    /**
     * 安全地创建一个文件
     *
     * @param path 绝对路径
     * @return 文件对象
     */
    public static File file(String path) throws IOException {
        return file(new File(path));
    }

    /**
     * 尝试创建一个空文件
     *
     * @param file 绝对路径
     * @return 文件对象
     */
    public static File file(@NotNull File file) throws IOException {
        if (file.exists()) {
            if (file.isFile()) {
                return file;
            } else {
                throw new FileAlreadyExistsException("File exists but is not a file: " + file);
            }
        } else {
            directory(file.getParentFile());
            if (file.createNewFile()) {
                return file;
            } else {
                throw new FileAlreadyExistsException("Create new file failed:" + file);
            }
        }
    }


    /**
     * 安全地创建一个文件夹
     *
     * @param dir 绝对地址
     * @return File对象
     */
    public static File directory(String dir) throws IOException {
        return directory(new File(dir));
    }

    /**
     * 尝试创建一个文件夹
     * <p>
     * Makes a directory, including any necessary but nonexistent parent
     * directories. If a file already exists with specified name but it is
     * not a directory then an IOException is thrown.
     * If the directory cannot be created (or does not already exist)
     * then an FileAlreadyExistsException is thrown.
     *
     * @param directory directory to create, must not be <code>null</code>
     * @throws FileAlreadyExistsException file already exists, but not a directory
     */
    public static File directory(@NotNull File directory) throws FileAlreadyExistsException {
        if (directory.exists()) {
            if (directory.isDirectory()) {
                return directory;
            } else {
                throw new FileAlreadyExistsException("File exists but is not a directory: " + directory);
            }
        } else {
            if (!directory.mkdirs()) {
                // Double-check that some other thread or process hasn't made
                // the directory in the background
                if (!directory.isDirectory()) {
                    throw new FileAlreadyExistsException("File exists but is not a directory: " + directory);
                }
            }
            return directory;
        }
    }

    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return File对象
     * @throws IllegalArgumentException 如果无效则抛出异常
     */
    public static File validFile(File file, String msg) {
        if (file.exists() && file.isFile()) {
            return file;
        } else {
            throw new IllegalArgumentException(msg);
        }
    }

    /**
     * 文件是否有效
     *
     * @param file File地址
     * @return File对象
     */
    public static File validFile(String file, String msg) {
        return validFile(new File(file), msg);
    }

    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return 是否有效
     */
    public static boolean isFile(File file) {
        return file.exists() && file.isFile();
    }

    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return 是否有效
     */
    public static boolean isFile(String file) {
        return isFile(new File(file));
    }


    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return File对象
     */
    public static File validDirectory(File file, String msg) {
        if (file.exists() && file.isDirectory()) {
            return file;
        }
        throw new IllegalArgumentException(msg);
    }

    /**
     * 文件是否有效
     *
     * @param file File地址
     * @return File对象
     * @throws IllegalArgumentException 如果无效则抛出异常
     */
    public static File validDirectory(String file, String msg) {
        return validDirectory(new File(file), msg);
    }

    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return 是否有效
     */
    public static boolean isDirectory(File file) {
        return file.exists() && file.isDirectory();
    }

    /**
     * 文件是否有效
     *
     * @param file File对象
     * @return 是否有效
     */
    public static boolean isDirectory(String file) {
        return isDirectory(new File(file));
    }

    /**
     * 检测子级文件是否在父级文件夹中
     *
     * @param p 父文件或目录
     * @param c 子文件或目录
     * @throws IllegalArgumentException 检查创建的子文件不在父目录中抛出此异常
     * @see FilenameUtils#isValidFilePath(String)
     */
    public static void checkSlip(@NotNull File p, @NotNull File c) throws IllegalArgumentException, IOException {
        String parentCanonicalPath = p.getCanonicalPath();
        String canonicalPath = c.getCanonicalPath();
        if (!canonicalPath.startsWith(parentCanonicalPath)) {
            throw new IllegalArgumentException("New file is outside of the parent dir: " + c.getName());
        }
    }

    public static void copyFile(String input, String output) throws IOException {
        copyFile(new File(input), new File(output));
    }

    public static void copyFile(File file, String output) throws IOException {
        copyFile(file, new File(output));
    }

    public static void copyFile(File file, File output) throws IOException {
        doCopyFile(file, output, true);
    }

    /**
     * 使用Channel优化拷贝
     *
     * @param srcFile          in
     * @param destFile         out
     * @param preserveFileDate 重新设置修改日期
     * @throws IOException -
     */
    public static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException {
        try (FileInputStream fis = new FileInputStream(srcFile);
             FileOutputStream fos = new FileOutputStream(destFile);
             FileChannel input = fis.getChannel();
             FileChannel output = fos.getChannel()) {
            long size = input.size(), pos = 0L;
            for (long count; pos < size; pos += output.transferFrom(input, pos, count)) {
                count = Math.min(size - pos, 31457280L);
            }
        }
        if (srcFile.length() != destFile.length()) {
            throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "'");
        } else {
            if (preserveFileDate && !destFile.setLastModified(srcFile.lastModified())) {
                throw new IOException("set last modified date failed: " + destFile);
            }
        }
    }


    public static void copyFile(InputStream input, String output) throws IOException {
        try (FileOutputStream os = new FileOutputStream(output)) {
            copyFile(input, os);
        }
    }

    public static void copyFile(InputStream input, File output) throws IOException {
        try (FileOutputStream os = new FileOutputStream(output)) {
            copyFile(input, os);
        }
    }

    public static void copyFile(String file, OutputStream output) throws IOException {
        try (InputStream is = IOUtils.openFileInputStream(file)) {
            copyFile(is, output);
        }
    }

    public static void copyFile(File file, OutputStream output) throws IOException {
        try (InputStream is = IOUtils.openFileInputStream(file)) {
            copyFile(is, output);
        }
    }

    public static void copyFile(InputStream input, OutputStream output) throws IOException {
        IOUtils.copyLarge(input, output);
    }

    public static void copyFile(String file, Writer out) throws IOException {
        try (FileReader r = new FileReader(file)) {
            copyFile(r, out);
        }
    }

    public static void copyFile(File file, Writer out) throws IOException {
        try (FileReader r = new FileReader(file)) {
            copyFile(r, out);
        }
    }


    public static void copyFile(Reader reader, String file) throws IOException {
        try (FileWriter writer = new FileWriter(file)) {
            copyFile(reader, writer);
        }
    }

    public static void copyFile(Reader reader, File file) throws IOException {
        try (FileWriter writer = new FileWriter(file)) {
            copyFile(reader, writer);
        }
    }

    public static void copyFile(Reader reader, Writer out) throws IOException {
        IOUtils.copyLarge(reader, out);
        out.flush();
    }


    /**
     * 移动文件或者目录
     *
     * @param src      源文件或者目录
     * @param tar      目标文件或者目录
     * @param override 是否覆盖文件
     */
    public static void move(File src, File tar, boolean override) throws IOException {
        if (tar.exists()) {
            if (override) {
                if (!tar.delete()) {
                    throw new FileAlreadyExistsException("file already exists and can not be delete: " + tar);
                }
            } else {
                return;
            }
        }
        if (!src.renameTo(tar)) {
            // 在文件系统不同的情况下，renameTo会失败，此时使用copy，然后删除原文件
            copyFile(src, tar);
            Asserts.state(src.delete(), "delete file failed!");
        }
    }

    /**
     * 读取成字符串
     *
     * @param file File对象
     * @return 字符串
     * @throws IOException -
     */
    public static String readAsString(File file, Charset charset) throws IOException {
        try (FileInputStream inputStream = new FileInputStream(file)) {
            return ReaderUtils.readAsString(inputStream, charset);
        }
    }

    /**
     * 读取成字符串
     *
     * @param file    File对象
     * @param data    需要写入的数据
     * @param charset the encoding to use, null means platform default
     * @throws IOException -
     */
    public static void writeString(File file, String data, Charset charset) throws IOException {
        try (FileOutputStream os = new FileOutputStream(file)) {
            WriterUtils.write(os, data, charset.name());
        }
    }

    /**
     * 读取成字符串
     *
     * @param file     File对象
     * @param data     需要写入的数据
     * @param encoding the encoding to use, null means platform default
     * @throws IOException -
     */
    public static void writeString(File file, String data, String encoding) throws IOException {
        try (FileOutputStream os = new FileOutputStream(file)) {
            WriterUtils.write(os, data, encoding);
        }
    }
}
